package cn.jbolt.core.base.config;

import cn.hutool.setting.Setting;
import cn.hutool.setting.SettingUtil;
import cn.jbolt.core.cache.JBoltCacheType;
import cn.jbolt.core.cache.caffeine.CaffeineCache;
import cn.jbolt.core.cache.redis.RedisCache;
import cn.jbolt.core.db.dialect.JBoltDMDialect;
import cn.jbolt.core.db.dialect.JBoltMysqlDialect;
import cn.jbolt.core.db.dialect.JBoltPostgresqlDialect;
import cn.jbolt.core.db.dialect.JBoltSqlServerDialect;
import cn.jbolt.core.db.sql.DBType;
import cn.jbolt.core.db.sql.SqlUtil;
import cn.jbolt.core.enjoy.directive.LikeValueDirective;
import cn.jbolt.core.enjoy.directive.SqlValueDirective;
import cn.jbolt.core.plugin.JBoltActiveRecordPlugin;
import cn.jbolt.core.util.JBoltDesUtil;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.jfinal.config.Plugins;
import com.jfinal.log.Log;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.CaseInsensitiveContainerFactory;
import com.jfinal.plugin.activerecord.dialect.AnsiSqlDialect;
import com.jfinal.plugin.activerecord.dialect.OracleDialect;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.template.Engine;
import com.jfinal.template.source.ClassPathSourceFactory;

import java.time.Duration;
import java.util.List;

/**
 * JBolt主库之外的其他数据库配置管理
 *
 * @ClassName: ExtendDatabaseConfig
 * @author: JFinal学院-小木 QQ：909854136
 * @date: 2020年3月3日
 */
public class JBoltExtendDatabaseConfig {
	private static final Log LOG = Log.getLog(JBoltExtendDatabaseConfig.class);
	private static final String SEPARATOR = "/";
	private final static String CONFIG_FILE_NAME = "extend_datasource.setting";
	private final static String CONFIG_FILE_NAME_PRO = "extend_datasource_pro.setting";
	private Setting setting;
	private static final JBoltExtendDatabaseConfig me = new JBoltExtendDatabaseConfig();

	public static JBoltExtendDatabaseConfig me() {
		return me;
	}

	private JBoltExtendDatabaseConfig() {
		if (JBoltConfig.pdevIsPro()) {
			setting = SettingUtil.get("dbconfig" + SEPARATOR + CONFIG_FILE_NAME_PRO);
		} else {
			setting = SettingUtil.get("dbconfig" + SEPARATOR + CONFIG_FILE_NAME);
		}
	}

	/**
	 * 核心配置
	 *
	 * @param me
	 * @param jboltProjectConfig
	 */
	public void config(Plugins me,JBoltProjectConfig jboltProjectConfig) {
		if (setting == null || setting.isEmpty()) {
			LOG.warn("扩展数据源配置文件" + (JBoltConfig.pdevIsPro() ? CONFIG_FILE_NAME_PRO : CONFIG_FILE_NAME) + "内暂无可用配置");
		} else {
			List<String> groups = setting.getGroups();
			for (String group : groups) {
				if (setting.getBool("enable", group, false)) {
					configDbPlugin(me, group, setting.getSetting(group),jboltProjectConfig);
				}
			}
		}
	}

	/**
	 * 设置方言
	 *
	 * @param arp
	 * @param dbType
	 */
	public static void setDialect(ActiveRecordPlugin arp, String dbType) {
		switch (dbType) {
		case DBType.MYSQL:
			arp.setDialect(new JBoltMysqlDialect());
			break;
		case DBType.ORACLE:
			arp.setDialect(new OracleDialect());
			break;
		case DBType.SQLSERVER:
			arp.setDialect(new JBoltSqlServerDialect());
			break;
		case DBType.POSTGRESQL:
			arp.setDialect(new JBoltPostgresqlDialect());
			break;
		case DBType.DM:
			arp.setDialect(new JBoltDMDialect());
			break;
		default:
			arp.setDialect(new AnsiSqlDialect());
			break;
		}
	}

	/**
	 * 配置数据库连接池等插件信息
	 *
	 * @param plugins
	 * @param configName
	 * @param dbSetting
	 * @param jboltProjectConfig
	 */
	private void configDbPlugin(Plugins plugins, String configName, Setting dbSetting,JBoltProjectConfig jboltProjectConfig) {
		// 配置数据库连接池插件
		DruidPlugin dbPlugin = createDruidPlugin(dbSetting);
		plugins.add(dbPlugin);
		// orm映射 配置ActiveRecord插件
		JBoltActiveRecordPlugin arp = new JBoltActiveRecordPlugin(configName,dbSetting.getStr("db_name"),dbSetting.getStr("db_schema"),dbPlugin);
		switch (JBoltConfig.JBOLT_CACHE_TYPE) {
		case JBoltCacheType.CAFFEINE:
			arp.setCache(new CaffeineCache());
			break;
		case JBoltCacheType.REDIS:
			arp.setCache(new RedisCache());
			break;
		}
		// 设置方言
		setDialect(arp, dbSetting.getStr("db_type", "mysql"));
		// 处理配置映射关系 扫描指定包 如果没有设置 默认扫描整个项目 但是会自动过滤掉 configName下的Model
		arp.addAutoBindPackage(dbSetting.get("model_package"));

		// 设置sql 引擎
		Engine sqlEngine = arp.getEngine();
		// 配置模板热加载
		sqlEngine.setDevMode(dbSetting.getBool("dev_mode", false));
		sqlEngine.setSourceFactory(new ClassPathSourceFactory());
//		sqlEngine.setStaticFieldExpression(true);
//		sqlEngine.setStaticFieldExpression(true);
		// 设置不区分大小写
		arp.setContainerFactory(new CaseInsensitiveContainerFactory(true));
		sqlEngine.setCompressorOn(' ');
		sqlEngine.addDirective("sqlValue", SqlValueDirective.class);
		sqlEngine.addDirective("likeValue", LikeValueDirective.class);
		sqlEngine.addSharedObject("SqlUtil", new SqlUtil());
		// 配置加载Sql模板的具体路径
		arp.addSqlTemplate(SEPARATOR + "sql" + SEPARATOR + dbSetting.getStr("db_type", "mysql") + SEPARATOR + "all_sqls.sql");

		// 添加到插件列表中
		plugins.add(dbPlugin);
		plugins.add(arp);
		// 调用扩展数据源配置
		jboltProjectConfig.configExtendDbPlugins(dbPlugin, arp, sqlEngine,configName, dbSetting);
	}

	/**
	 * 创建一个Druid配置插件
	 *
	 * @param configName
	 * @return
	 */
	public static DruidPlugin createDruidPlugin(String configName) {
		Setting setting = me().getSetting();
		if (setting == null || setting.isEmpty()) {
			return null;
		}
		Setting dbSetting = setting.getSetting(configName);
		if (dbSetting == null || dbSetting.isEmpty()) {
			return null;
		}
		return createDruidPlugin(dbSetting);
	}

	/**
	 * 创建一个Druid配置插件
	 *
	 * @param dbSetting
	 * @return
	 */
	public static DruidPlugin createDruidPlugin(Setting dbSetting) {
		String user = dbSetting.get("user");
		String password = dbSetting.get("password");
		boolean is_encrypted = dbSetting.getBool("is_encrypted",false);
		if(is_encrypted){
			user = JBoltDesUtil.getDecryptData(user);
			password = JBoltDesUtil.getDecryptData(password);
		}
		DruidPlugin druidPlugin = new DruidPlugin(dbSetting.get("jdbc_url"), user,password);
		String dbType = dbSetting.getStr("db_type", DBType.MYSQL);
		//TODO mmm 这里dm不能配置wallfilter
		if(!DBType.isDM(dbType)){
			// 配置防火墙
			WallFilter wallFilter = new WallFilter(); // 加强数据库安全
			wallFilter.setDbType(dbType);
			if (DBType.isOracle(dbType)) {
				// oracle关闭FunctionCheck
				WallConfig wallConfig = wallFilter.getConfig();
				if (wallConfig == null) {
					wallConfig = new WallConfig();
					wallFilter.setConfig(wallConfig);
				}
				wallConfig.setFunctionCheck(false);
			}
			druidPlugin.addFilter(wallFilter);
		}
		// 统计监控的过滤器
		StatFilter statFilter = new StatFilter();
		statFilter.setMergeSql(true);
		statFilter.setDbType(dbType);
		statFilter.setLogSlowSql(true);
		statFilter.setSlowSqlMillis(Duration.ofMillis(1000).toMillis());
		// 添加 StatFilter 才会有统计数据
		druidPlugin.addFilter(statFilter);

		if(JBoltConfig.DEV_MODE && JBoltConfig.JBOLT_DRUID_DEV_MODE_FULL_SQL_LOG){
			// 2.日志插件
			// 保存DruidDataSource的监控记录,设置打印日志周期,默认使用DruidDataSourceStatLoggerImpl
			druidPlugin.setTimeBetweenLogStatsMillis(24 * 60 * 60 * 1000);
//			druidPlugin.setConnectionProperties("druid.timeBetweenLogStatsMillis=" + Duration.ofHours(24).toMillis());
			Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
			slf4jLogFilter.setConnectionLogEnabled(false);
			slf4jLogFilter.setResultSetLogEnabled(false);
			slf4jLogFilter.setStatementParameterSetLogEnabled(false);
			slf4jLogFilter.setConnectionLogEnabled(false);
			slf4jLogFilter.setResultSetCloseAfterLogEnabled(false);
			slf4jLogFilter.setConnectionCloseAfterLogEnabled(false);
			slf4jLogFilter.setStatementParameterClearLogEnable(false);
			slf4jLogFilter.setStatementPrepareAfterLogEnabled(false);
			slf4jLogFilter.setStatementPrepareCallAfterLogEnabled(false);
			slf4jLogFilter.setStatementCreateAfterLogEnabled(false);
			slf4jLogFilter.setStatementCloseAfterLogEnabled(false);

			// 设置输出执行后的日志 带执行耗时等信息
			slf4jLogFilter.setStatementExecuteAfterLogEnabled(false);
			// 设置批量操作执行后的日志 带执行耗时等信息
			slf4jLogFilter.setStatementExecuteBatchAfterLogEnabled(false);
			// 设置查询操作执行后的日志 带执行耗时等信息
			slf4jLogFilter.setStatementExecuteQueryAfterLogEnabled(false);
			// 设置更新 插入 删除 操作执行后的日志 带执行耗时等信息
			slf4jLogFilter.setStatementExecuteUpdateAfterLogEnabled(false);
			// 输出完整的SQL 将值替换掉问号，这个仅在开发模式下有效
			slf4jLogFilter.setStatementExecutableSqlLogEnable(true);

			druidPlugin.addFilter(slf4jLogFilter);
		}
		if (dbSetting.getStr("db_type", "mysql").equals(DBType.MYSQL)) {
			// 指定初始化 编码为utf8mb4
			druidPlugin.setConnectionInitSql("set names utf8mb4");
		}
		return druidPlugin;
	}

	public Setting getSetting() {
		return setting;
	}
}
